package com.sparrow.common.buff.domain;

import com.sparrow.common.buff.context.BuffContext;
import com.sparrow.common.buff.effect.IBuffEffect;
import com.sparrow.common.buff.listener.BuffListener;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Buff实例
 */
public class Buff {
    private static final AtomicLong ID_GENERATOR = new AtomicLong();
    /**
     * 唯一id
     */
    private final long uniqueId;
    /**
     * 配置id
     */
    private final int configId;
    /**
     * 持续时间
     */
    private final long duration;  // ms
    /**
     * 最大堆叠数量
     */
    private final int maxStack;
    /**
     * 结束时间
     */
    private long endTime;
    /**
     * 当前叠加次数
     */
    private int stackCount;
    /**
     * buff效果 控制行为
     */
    private IBuffEffect effect;
    /**
     * 上次tick时间
     */
    private long lastTickTime;

    /**
     * 不叠加的时候 是否替换旧的
     */
    private boolean replace;
    /**
     * tick间隔
     */
    private long tickInterval = 1000;
    /**
     * buff上下文
     */
    private BuffContext buffContext;

    private static final int MAX_TICKS_PER_CALL = 100;

    private final List<BuffListener> listeners = new ArrayList<>();

    public Buff(int configId, long duration, int maxStack, long now) {
        this.uniqueId = ID_GENERATOR.incrementAndGet();
        this.configId = configId;
        this.duration = duration;
        this.maxStack = maxStack;
        this.stackCount = 1;
        this.endTime = now + duration;
        this.lastTickTime = now;
    }

    public void setBuffContext(BuffContext buffContext) {
        this.buffContext = buffContext;
    }

    public BuffContext getBuffContext() {
        return buffContext;
    }

    public void addListener(BuffListener listener) {
        listeners.add(listener);
    }

    private void notifyAdded() {
        for (BuffListener l : listeners) l.onBuffAdded(this);
    }

    private void notifyExpired() {
        for (BuffListener l : listeners) l.onBuffExpire(this);
    }

    public boolean canStack() {
        return maxStack > 1;
    }

    public boolean canReplace() {
        return replace;
    }

    public void setEffect(IBuffEffect effect) {
        this.effect = effect;
    }

    public void refresh(long now) {
        this.endTime = now + duration;
        onRefresh();
    }

    public void stack(long now) {
        if (stackCount < maxStack) {
            stackCount++;
        }
        refresh(now);
    }

    public boolean isExpired(long now) {
        return now >= endTime;
    }

    public long getUniqueId() {
        return uniqueId;
    }

    public int getConfigId() {
        return configId;
    }

    public int getStackCount() {
        return stackCount;
    }

    public long getDuration() {
        return duration;
    }

    public int getMaxStack() {
        return maxStack;
    }

    public long getEndTime() {
        return endTime;
    }

    public void setEndTime(long endTime) {
        this.endTime = endTime;
    }

    public void setStackCount(int stackCount) {
        this.stackCount = stackCount;
    }

    public long getLastTickTime() {
        return lastTickTime;
    }

    public void setLastTickTime(long lastTickTime) {
        this.lastTickTime = lastTickTime;
    }

    public boolean isReplace() {
        return replace;
    }

    public void setReplace(boolean replace) {
        this.replace = replace;
    }

    public void onStart() {
        notifyAdded();
        effect.onStart(this);
    }

    public void onRefresh() {
        effect.onRefresh(this);
    }

    public void onExpire() {
        notifyExpired();
        effect.onExpire(this);
    }

    public void onTick(long now) {
        long elapsed = now - lastTickTime;
        int ticksToRun = Math.min((int)(elapsed / tickInterval), MAX_TICKS_PER_CALL);
        for (int i = 1; i <= ticksToRun; i++) {
            long tickTime = lastTickTime + i * tickInterval;
            effect.onTick(this, tickTime);
        }
        lastTickTime += ticksToRun * tickInterval;
    }

    public long getRemainingTime(long now) {
        return Math.max(0, endTime - now);
    }

}
